package cn.finedo.service;

import cn.finedo.Enum.AiLabelEnum;
import cn.finedo.common.NonUtil;
import cn.finedo.dto.DirDataDto;
import cn.finedo.dto.LableDto;
import cn.finedo.dto.PicMarkInfoDto;
import cn.finedo.util.DirDataUtil;
import cn.finedo.common.ReturnValueDomain;
import cn.finedo.dto.AbilityDirDataDto;
import cn.finedo.entity.AbilityDirEntity;
import cn.finedo.mapper.AIAbilityDataDirMapper;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.velocity.shaded.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Service
public class AbilityDataService {

    // 日志打印
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Value("${fileconfig.maxsize}")
    private String maxsize;
    @Value("${fileconfig.filepath}")
    private String fileBasePath;
    @Value("${fileconfig.supportfiletype}")
    private String supportType;
    @Value("${fileconfig.sendurl}")
    private String sendUrl;
    @Value("${fileconfig.token}")
    private String xAccesstoken;
    @Autowired
    private AIAbilityDataDirMapper abilityDataDirMapper;


    /**
     * 上传文件到临时文件夹
     * @param dto
     * @return
     */
    public ReturnValueDomain<AbilityDirDataDto> uploadAbilityImage(DirDataDto dto) {
        ReturnValueDomain<AbilityDirDataDto> ret = new ReturnValueDomain<>();
        AbilityDirDataDto abilityDirDataDto = new AbilityDirDataDto();
        abilityDirDataDto.setAino(dto.getAino());
        try {
            // 查询能力对应的文件夹
            ReturnValueDomain<AbilityDirEntity> queryResult = queryAbilityDir(abilityDirDataDto);
            if (queryResult.isFail()) {
                return ret.setFail(queryResult.getResultdesc());
            }
            AbilityDirEntity entity = queryResult.getObject();
            String dirPath = getDirectoryPath(entity, dto);
            // 处理文件夹和文件保存
            Boolean checkResult = handleDirectoryAndFiles(dto, dirPath, abilityDirDataDto);
            // 保存编码对应的文件夹
            if (NonUtil.isNon(entity)) {
                AbilityDirEntity dirEntity = createAbilityDirEntity(abilityDirDataDto, dirPath);
                abilityDataDirMapper.insert(dirEntity);
            }
            if (!checkResult) {
                return ret.setSuccess("部分文件由于不符合条件跳过，其它上传成功", abilityDirDataDto);
            }
            return ret.setSuccess("文件上传成功", abilityDirDataDto);
        } catch (IOException e) {
            logger.error("文件上传失败", e);
            return ret.setFail("文件上传失败");
        }
    }

    /**
     * 处理文件，将文件保存到临时文件夹内
     * @param dto
     * @param dirPath
     * @throws IOException
     */
    private Boolean handleDirectoryAndFiles(DirDataDto dto, String dirPath, AbilityDirDataDto abilityDirdataDto) throws IOException {
        Boolean checkResult = true;
        String uuidStr = getUUIDString("image", 15);
        // 保存文件到临时文件夹
        Path tempDir = Paths.get(dirPath + File.separator + "temp" + File.separator + uuidStr);
        if (!Files.exists(tempDir)) {
            Files.createDirectories(tempDir);
        }
        List<String> fileNameList = dto.getFileNameList();
        List<String> imageBase64List = dto.getImageBase64List();
        List<String> supportTypeList = Arrays.asList(this.supportType.split(","));
        for (int i=0; i < imageBase64List.size(); i++) {
            String fileName = fileNameList.get(i);
            byte[] imageBytes = Base64.getDecoder().decode(imageBase64List.get(i));
            // 校验文件类型和大小
            String fileType = FilenameUtils.getExtension(fileName).toLowerCase();
            if (imageBytes.length > Integer.parseInt(maxsize)) {
                logger.warn("文件 " + fileName + " 超过最大允许大小，已跳过。");
                checkResult = false;
                continue;
            }
            if (!supportTypeList.contains(fileType)) {
                logger.warn("不支持的文件类型，已跳过");
                checkResult = false;
                continue;
            }
            // 保存文件
            Path targetLocation = tempDir.resolve(fileName);
            Files.write(targetLocation, imageBytes);
        }
        abilityDirdataDto.setTempDirUUID(uuidStr);
        abilityDirdataDto.setDirid(uuidStr);
        return checkResult;
    }


    /**
     * 查询图片并渲染
     * @param dto
     * @return
     */
    public void queryPictureToMakeSence(HttpServletResponse response, AbilityDirDataDto dto) {
        try {
            // 查询能力对应的文件夹
            ReturnValueDomain<AbilityDirEntity> queryResult = queryAbilityDir(dto);
            if (queryResult.isFail()) {
                logger.error("能力对应的文件夹不存在");
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                response.getWriter().write("能力对应的文件夹不存在");
                return;
            }
            AbilityDirEntity entity = queryResult.getObject();
            if (entity == null) {
                logger.error("未查询到对应的数据集");
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                response.getWriter().write("能力对应的数据集不存在");
                return;
            }
            String dirPath = entity.getDirpath();
            String uuidStr = dto.getTempDirUUID();
            Path tempDir = Paths.get(dirPath + File.separator + "temp" + File.separator + uuidStr);
            if (!Files.exists(tempDir)) {
                logger.error("不存在的数据集或数据集内容为空");
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                response.getWriter().write("不存在的数据集或数据集内容为空");
                return;
            }

            response.setContentType("application/zip");
            response.setHeader("Content-Disposition", "attachment; filename=\"images.zip\"");
            try (ServletOutputStream outputStream = response.getOutputStream();
                 ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
                Files.list(tempDir).filter(Files::isRegularFile).forEach(path -> {
                    try {
                        ZipEntry zipEntry = new ZipEntry(path.getFileName().toString());
                        zipOutputStream.putNextEntry(zipEntry);
                        try (InputStream fileStream = Files.newInputStream(path)) {
                            byte[] buffer = new byte[1024];
                            int length;
                            while ((length = fileStream.read(buffer)) > 0) {
                                zipOutputStream.write(buffer, 0, length);
                            }
                        }
                        zipOutputStream.closeEntry();
                    } catch (Exception e) {
                        logger.error("添加到ZIP文件时发生错误", e);
                    }
                });
                outputStream.flush();
            }
        } catch (Exception e) {
            logger.error("读取图片文件或创建ZIP文件失败", e);
            throw new RuntimeException("处理请求时发生错误", e);
        }
    }

    private String getContentType(String ext) {
        switch (ext.toLowerCase()) {
            case ".jpg":
            case ".jpeg":
                return "image/jpeg";
            case ".png":
                return "image/png";
            case ".gif":
                return "image/gif";
            default:
                return "application/octet-stream";
        }
    }

    /**
     * 根据能力编码查询对应的标签信息
     * @param dto
     * @return
     */
    public ReturnValueDomain<AbilityDirDataDto> queryLabelsByAino(AbilityDirDataDto dto) {
        ReturnValueDomain<AbilityDirDataDto> ret = new ReturnValueDomain<>();
        // 根据能力查询对应的标签信息
        try {
            AiLabelEnum aiLabelEnum = AiLabelEnum.valueOf(dto.getAino().toLowerCase());
            List<String> labels = aiLabelEnum.getLabels();
            List<LableDto> LabelsList = new ArrayList<>(labels.size());
            generateRandomColors(labels, LabelsList);
            dto.setLabelList(LabelsList);
        } catch (IllegalArgumentException e) {
            logger.error("根据能力编码查询标签错误，能力编码[{}]", dto.getAino(), e);
            return ret.setFail("请核对能力编码后再试");
        } catch (Exception e) {
            logger.error("根据能力编码查询标签错误，能力编码[{}]", dto.getAino(), e);
            return ret.setFail("查询编码错误");
        }
        return ret.setSuccess("能力对应的标签信息上传成功", dto);
    }

    /**
     * 生成随机颜色
     * @param labels
     * @param lableDtoList
     */
    public void generateRandomColors(List<String> labels, List<LableDto> lableDtoList) {
        List<Map<String, String>> colors = new ArrayList<>();
        HashSet<String> uniqueColors = new HashSet<>();
        int count = labels.size();
        Random random = new Random();
        labels.stream().forEach(e -> {
            LableDto dto = new LableDto();
            dto.setColor(String.format("#%06X", random.nextInt(0xFFFFFF)));
            dto.setId(UUID.randomUUID().toString());
            dto.setName(e);
            lableDtoList.add(dto);
        });
    }

    /**
     * 查询能力对应的数据集
     * @param dirdata
     * @return
     */
    public ReturnValueDomain<AbilityDirEntity> queryAbilityDir(AbilityDirDataDto dirdata) {
        ReturnValueDomain<AbilityDirEntity> ret = new ReturnValueDomain<>();
        LambdaQueryWrapper<AbilityDirEntity> qw = new LambdaQueryWrapper<>();
        qw.eq(AbilityDirEntity::getAino, dirdata.getAino());
        AbilityDirEntity entity = null;
        try {
            entity = abilityDataDirMapper.selectOne(qw);
        } catch (Exception e) {
            logger.error("查询能力对应数据集目录失败！！！", e);
            return ret.setFail("查询能力对应数据集目录失败");
        }
        return ret.setSuccess("查询能力对应数据集目录成功", entity);
    }

    /**
     * 将临时文件移动到训练文件夹，保存标注信息，删除临时文件
     * @param dto
     * @return
     */
    public ReturnValueDomain<String> savePicAndMarkInfo(AbilityDirDataDto dto) {
        ReturnValueDomain<String> ret = new ReturnValueDomain<>();
        String strAllPicResult = dto.getPicresult();
        if (NonUtil.isNon(strAllPicResult)) {
            return ret.setFail("标注信息为空");
        }
        List<PicMarkInfoDto> picMarkInfoDtoList = JSONArray.parseArray(strAllPicResult, PicMarkInfoDto.class);
        if (NonUtil.isNon(picMarkInfoDtoList)) {
            return ret.setFail("标注信息为空");
        }
        ReturnValueDomain<String> sendMarkInfoResult = sendMarkInfo(picMarkInfoDtoList);
        if (NonUtil.isNon(sendMarkInfoResult) || sendMarkInfoResult.isFail()) {
            return ret.setFail("发送标注信息失败");
        }
        ReturnValueDomain<AbilityDirEntity> result = queryAbilityDir(dto);
        if (result.isFail() || NonUtil.isNon(result.getObject())) {
            return ret.setFail("查询能力对应数据集目录失败");
        }
        AbilityDirEntity entity = result.getObject();
        String dirPath = entity.getDirpath();
        String imageTempDir = dirPath + File.separator + "temp" + File.separator + dto.getTempDirUUID();

        try {
            moveMarkedImages(imageTempDir, dirPath, picMarkInfoDtoList);
            createAndSaveMarkInfo(dirPath, picMarkInfoDtoList);
            cleanupTempDir(imageTempDir);
        } catch (IOException e) {
            logger.error("处理图片和标注信息时发生错误", e);
            return ret.setFail("保存图片文件失败");
        }
        return ret.setSuccess("保存图片文件成功");
    }

    /**
     * 将标注的临时文件转移到能力目录下
     * @param sourceDir
     * @param targetDir
     * @param picMarkInfoDtoList
     * @return
     * @throws IOException
     */
    private ReturnValueDomain<String> moveMarkedImages(String sourceDir, String targetDir, List<PicMarkInfoDto> picMarkInfoDtoList) throws IOException {
        ReturnValueDomain<String> ret = new ReturnValueDomain<>();
        // 获取已标注图片的名称集合
        List<String> markedList = picMarkInfoDtoList.stream().map(PicMarkInfoDto::getPicid).collect(Collectors.toList());
        File source = new File(sourceDir);
        if (!source.exists() || !source.isDirectory()) {
            return ret.setFail("未查询到临时图片文件目录");
        }
        File[] files = source.listFiles();
        if (files == null) {
            return ret.setFail("未查询到临时图片文件");
        }
        for (File file : files) {
            String fileNameWithoutExtension = getFileNameWithoutExtension(file.getName());
            if (markedList.contains(fileNameWithoutExtension)) {
                try {
                    Files.move(file.toPath(), Paths.get(targetDir, file.getName()), StandardCopyOption.REPLACE_EXISTING);
                } catch (IOException e) {
                    logger.error("移动文件失败", e);
                    return ret.setFail("保存图片文件失败");
                }
            }
        }
        return ret.setSuccess("移动标注的临时图片文件成功");
    }

    /**
     * 移动标注的临时文件
     * @param dirPath
     * @param picMarkInfoDtoList
     * @return
     * @throws IOException
     */
    private ReturnValueDomain<String> createAndSaveMarkInfo(String dirPath, List<PicMarkInfoDto> picMarkInfoDtoList) throws IOException {
        ReturnValueDomain<String> ret = new ReturnValueDomain<>();
        // 实现创建目录和保存标注信息的逻辑
        String markInfoDirPath = dirPath + File.separator + "label";
        try {
            Files.createDirectories(Paths.get(markInfoDirPath));
        } catch (IOException e) {
            logger.error("创建标注信息目录失败", e);
            return ret.setFail("创建标注信息目录失败");
        }
        // 将标注信息保存到label目录下，相同名称的覆盖
        for (PicMarkInfoDto picMarkInfoDto : picMarkInfoDtoList) {
            String picId = picMarkInfoDto.getPicid();
            List<String> coordinates = Arrays.asList(picMarkInfoDto.getPicresult().split(","));
            String fileName = picId + ".txt";
            String filePath = markInfoDirPath + File.separator + fileName;
            try {
                // 使用create 和 write 选项来确保文件被创建或替换，并从头开始写入
                Files.write(Paths.get(filePath), Collections.emptyList(), StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
                //将坐标信息写入文件，每个坐标后换行
                for (String coordinate : coordinates) {
                    Files.write(Paths.get(filePath), Collections.singleton(coordinate), StandardCharsets.UTF_8, StandardOpenOption.APPEND);
                }
            } catch (IOException e) {
                logger.error("保存标注信息失败", e);
                return ret.setFail("保存标注信息失败");
            }
        }
        return ret.setSuccess("保存标注信息成功");
    }

    /**
     * 清理临时文件夹
     * @param tempDir
     */
    private void cleanupTempDir(String tempDir) {
        // 实现清理临时目录的逻辑
        File imageDir = new File(tempDir);
        File[] dirFiles = imageDir.listFiles();
        if (NonUtil.isNon(dirFiles)) {
            return;
        }
        for (File file : dirFiles) {
            if (file.isFile()) {
                if (file.delete()) {
                    logger.info("删除临时文件成功：" + file.getName());
                } else {
                    logger.info("删除临时文件失败：" + file.getName());
                }
            }
        }
        if (imageDir.delete()) {
            logger.info("删除临时目录成功：" + imageDir.getName());
        } else {
            logger.info("删除临时目录失败：" + imageDir.getName());
        }
    }

    /**
     * 提取文件不带扩展名
     * @param fileName
     * @return
     */
    private static String getFileNameWithoutExtension(String fileName) {
        return fileName.substring(0, fileName.lastIndexOf("."));
    }

    /**
     * 发送坐标信息
     * @param picMarkInfoDtoList
     * @return
     */
    public ReturnValueDomain<String> sendMarkInfo(List<PicMarkInfoDto> picMarkInfoDtoList) {
        ReturnValueDomain<String> ret = new ReturnValueDomain<>();
        // 创建调用第三方请求头
        for (PicMarkInfoDto infoDto : picMarkInfoDtoList) {
            JSONObject requestJson = new JSONObject();
            requestJson.put("files", infoDto.getPicid());
            requestJson.put("photooverlays", infoDto.getPicresult().split(","));
            logger.info("sendinfo: " + requestJson.toJSONString());
            try {
                URL url = new URL(sendUrl);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("POST");
                //发送POST请求必须设置为true
                conn.setDoOutput(true);
                conn.setDoInput(true);
                //设置连接超时时间和读取超时时间
                conn.setConnectTimeout(20000);
                conn.setReadTimeout(20000);
                conn.setRequestProperty("Content-Type", "application/json");
                conn.setRequestProperty("Accept", "application/json");
                conn.setRequestProperty("X-Access-Token", xAccesstoken);
                try (OutputStream os = conn.getOutputStream()) {
                    os.write(requestJson.toJSONString().getBytes("utf-8"));
                    os.flush();
                }
//                // 获取返回内容
                int code = conn.getResponseCode();
                String message = conn.getResponseMessage();
                logger.info("message:[{}],code:[{}]", message, code);
                if (200 == code) {
                    try (BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"))) {
                        StringBuilder result = new StringBuilder();
                        String line;
                        while ((line = in.readLine()) != null) {
                            result.append(line);
                        }
                        logger.info("===========result=========" + result);
                        JSONObject resultObj = JSONObject.parseObject(result.toString());
                        if (!resultObj.getBoolean("isSuccess")) {
                            String errorInfo = resultObj.getString("errorInfo");
                            return ret.setFail("发送坐标信息失败，错误信息[{}]" + errorInfo);
                        }
                    }
                } else {
                    return ret.setFail("发送坐标信息失败，返回码[{}]", "" + code);
                }
            } catch (Exception e) {
                logger.error("调用第三方接口失败", e);
                return ret.setFail("发送坐标信息失败");
            }
        }
        return ret.setSuccess("发送坐标信息成功");
    }

    /**
     * 获取创建文件的目录
     * @param entity
     * @param dto
     * @return
     */
    private String getDirectoryPath(AbilityDirEntity entity, DirDataDto dto) {
        if (entity == null) {
            String baseDirPath = DirDataUtil.getMountDirectory(fileBasePath);
            return baseDirPath + File.separator + dto.getAino();
        } else {
            return entity.getDirpath();
        }
    }

    /**
     * 将base64图片内容存储为文件
     * @param filePath
     * @param base64Data
     * @throws IOException
     */
    private void saveFileFromBase64(String filePath, String base64Data) throws IOException {
        byte[] imageBytes = Base64.getDecoder().decode(base64Data.split(",")[1]);
        Files.write(Paths.get(filePath), imageBytes);
    }

    /**
     * 创建能力目录实体
     * @param dto
     * @param dirPath
     * @return
     */
    private AbilityDirEntity createAbilityDirEntity(AbilityDirDataDto dto, String dirPath) {
        AbilityDirEntity insertEntity = new AbilityDirEntity();
        insertEntity.setAino(dto.getAino());
        insertEntity.setDirid(dto.getDirid());
        insertEntity.setDirpath(dirPath);
        return insertEntity;
    }

    /**
     * 获取uuid
     * @param prefix
     * @param length
     * @return
     */
    private String getUUIDString(String prefix, int length) {
        UUID uuid = UUID.randomUUID();
        String uuidStr = uuid.toString().replace("-", "");
        if (uuidStr.length() > length) {
            uuidStr = uuidStr.substring(0, length);
        }
        return prefix + uuidStr;
    }

}
